package street;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class IpStreet {
	private static IpStreet instance = null;
	private MappedByteBuffer[] data;
	private int[] prefStart = new int[256];
	private int[] prefEnd = new int[256];
	private int[] endArr;

	public static synchronized IpStreet getInstance() throws IOException {
		if (instance == null)
			instance = new IpStreet();
		return instance;
	}

	public IpStreet() throws IOException {
		String file_name = "C:\\Users\\hh\\Desktop\\fsdownload\\ipdatacloud_street.dat";
		RandomAccessFile raf = new RandomAccessFile(file_name, "r");
		FileChannel channel = raf.getChannel();
		try {
			long size = channel.size();
			int numChunks = (int) Math.ceil((double) size / Integer.MAX_VALUE);
			data = new MappedByteBuffer[numChunks];

			long remaining = size;
			int chunkIndex = 0;
			while (remaining > 0) {
				int chunkSize = (int) Math.min(remaining, Integer.MAX_VALUE);
				data[chunkIndex] = channel.map(FileChannel.MapMode.READ_ONLY, chunkIndex * (long) Integer.MAX_VALUE, chunkSize);
				chunkIndex++;
				remaining -= chunkSize;
			}


			int record_size = unpackInt4byte(0);
			int i = 0;
			while (i < 256) {
				int p = i * 8 + 4;
				prefStart[i] = unpackInt4byte(p);
				prefEnd[i] = unpackInt4byte(p + 4);
				i += 1;
			}
			endArr = new int[record_size];
			int j = 0;
			while (j < record_size) {
				int p = 2052 + (j * 14);
				endArr[j] = unpackInt4byte(p);
				j += 1;
			}
		} finally {
			channel.close();
			raf.close();
		}
	}

	public String get(String ip) throws Exception {
		String[] ipdot = ip.split("\\.");
		int prefix = Integer.parseInt(ipdot[0]);
		if (prefix < 0 || prefix > 255 || ipdot.length != 4) {
			throw new Exception("invalid ip address");
		}
		int intIP = ipToInt(ip);
		int low = prefStart[prefix];
		int high = prefEnd[prefix];
		int cur = low == high ? low : search(low, high, intIP);
		return getAddr(cur);
	}

	private int search(int low, int high, int k) {
		int M = 0;
		while (low <= high) {
			int mid = (low + high) / 2;
			int end_ip_num = endArr[mid];
			if (end_ip_num >= k) {
				M = mid;
				if (mid == 0) {
					break;
				}
				high = mid - 1;
			} else {
				low = mid + 1;
			}
		}
		return M;
	}

	private int ipToInt(String ip) throws UnknownHostException {
		byte[] _ip = InetAddress.getByName(ip).getAddress();
		return ((_ip[0] << 24) & 0xFF000000) | ((_ip[1] << 16) & 0x00FF0000) | ((_ip
				[2] << 8) & 0x0000FF00) | (_ip[3] & 0x000000FF);
	}

	private int unpackInt4byte(int offset) {
		byte[] bytes = new byte[4];
		getBytes(bytes, offset);
		return ((bytes[0] & 0xFF) << 0) | ((bytes[1] & 0xFF) << 8) | ((bytes[2] & 0xFF) << 16)
				| ((bytes[3] & 0xFF) << 24);
	}

	private String getAddr(int j) throws UnsupportedEncodingException {
		int p = 2052 + (j * 14);
		long offset = unpackInt8byte(4 + p);
		int length = unpackInt2byte(12 + p);
		byte[] bytes = new byte[length];
		getBytes(bytes, offset);
		return new String(bytes, "UTF-8");
	}

	private long unpackInt8byte(int offset) {
		byte[] bytes = new byte[8];
		getBytes(bytes, offset);
		return ((long) (bytes[0] & 0xFF) << 0) | ((long) (bytes[1] & 0xFF) << 8) | ((long) (bytes[2] & 0xFF) << 16)
				| ((long) (bytes[3] & 0xFF) << 24) | ((long) (bytes[4] & 0xFF) << 32) | ((long) (bytes[5] & 0xFF) << 40)
				| ((long) (bytes[6] & 0xFF) << 48) | ((long) (bytes[7] & 0xFF) << 56);
	}

	private int unpackInt2byte(int offset) {
		byte[] bytes = new byte[2];
		getBytes(bytes, offset);
		return ((bytes[0] & 0xFF) << 0) | ((bytes[1] & 0xFF) << 8);
	}

	private void getBytes(byte[] bytes, long offset) {
		int off = 0;
		int chunkIndex = (int) (offset / Integer.MAX_VALUE);
		int position = (int) (offset % Integer.MAX_VALUE);
		MappedByteBuffer chunk = data[chunkIndex];
		chunk.position(position);
		chunk.get(bytes);
	}
}
